﻿using System;
using System.Collections.Generic;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions.Readers;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions
{
    /// <summary>
    /// Represents a model of boolean function (fully determined or partial).
    /// Function determines by Domain1 and Domain0. All other values are unspecified.
    /// </summary>
    public class BooleanFunction : ICloneable, IComparable
    {
        // Fields

        /// <summary>
        /// Domain of 0 values.
        /// </summary>
        public CubeSet Domain0 { get; set; }

        /// <summary>
        /// Domain of 1 values.
        /// </summary>
        public CubeSet Domain1 { get; set; }

        /// <summary>
        /// BooleanFunction object reader
        /// </summary>
        private BooleanFunctionReader _reader;

        /// <summary>
        /// BooleanFunction object reader
        /// </summary>
        public BooleanFunctionReader Reader
        {
            get
            {
                return _reader;
            }
            set
            {
                _reader = value;
                _reader.Function = this;
            }
        }

        /// <summary>
        /// Boolean function dimension.
        /// </summary>
        public int Dimension
        {
            get
            {
                if (!CheckConsistency())
                {
                    throw new InvalidOperationException("Consistency checking failed!");
                }

                var dimension = Domain0.Select(cube => cube.Dimension).FirstOrDefault();

                foreach (var cube in Domain1)
                {
                    if (dimension != 0)
                    {
                        break;
                    }

                    dimension = cube.Dimension;
                    break;
                }

                return dimension;
            }
        }
        
        // Methods

        /// <summary>
        /// Default constructor.
        /// </summary>
        public BooleanFunction()
        {
            Domain0 = new CubeSet();
            Domain1 = new CubeSet();
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="booleanFunction">Boolean function to copy values from.</param>
        public BooleanFunction(BooleanFunction booleanFunction)
        {
            // copying values
            Domain0 = booleanFunction.Domain0;
            Domain1 = booleanFunction.Domain1;
        }

        /// <summary>
        /// Clones boolean function in terms of it values.
        /// </summary>
        /// <returns>Object's clone.</returns>
        public object Clone()
        {
            var clone = new BooleanFunction(this);
            return clone;
        }

        /// <summary>
        /// Compares boolean function with arbitrary object.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (!(obj is BooleanFunction))
            {
                // could not compare
                throw new ArgumentException("Allowed only complare with another boolean function.");
            }

            // comparing values
            var booleanFunction = (BooleanFunction) obj;

            // calculating square of 1 value domain
            var square1 = Domain1.Sum(cube => Math.Pow(2, cube.Length - cube.Rank));

            // calculating square of 1 value domain for object
            var square2 = booleanFunction.Domain1.Sum(cube => Math.Pow(2, cube.Length - cube.Rank));

            var result = (int) (square1 - square2); 
            return result;
        }

        /// <summary>
        /// Converts boolean function object to string representation.
        /// </summary>
        /// <returns>String representation.</returns>
        public override string ToString()
        {
            if (!CheckConsistency())
            {
                throw new InvalidOperationException("Could not write function due to consistency checking failed!");
            }

            var value = Domain0.Aggregate(String.Empty, (current, cube) => current + (cube + " 0\r\n"));

            return Domain1.Aggregate(value, (current, cube) => current + (cube + " 1\r\n"));
        }

        /// <summary>
        /// Implicitely converts boolean function to string representation.
        /// </summary>
        /// <param name="booleanFunction">Boolean function to convert to string.</param>
        /// <returns>String representation.</returns>
        public static implicit operator string(BooleanFunction booleanFunction)
        {
            return booleanFunction.ToString();
        }

        /// <summary>
        /// Checks whether two boolean functions are equal.
        /// </summary>
        /// <param name="booleanFunction">Boolean function to compare with.</param>
        /// <returns>True if boolean functions are equal.</returns>
        public bool Equals(BooleanFunction booleanFunction)
        {
            if (ReferenceEquals(null, booleanFunction)) return false;
            return ReferenceEquals(this, booleanFunction) || 
                (Equals(booleanFunction.Domain0, Domain0) && 
                 Equals(booleanFunction.Domain1, Domain1));
        }

        /// <summary>
        /// Returns hash code for boolean function.
        /// </summary>
        /// <returns>Integer hash code.</returns>
        public override int GetHashCode()
        {
            return Domain0.GetHashCode()*Domain1.GetHashCode();
        }

        /// <summary>
        /// Determines whether specified object is equal to the current object.
        /// </summary>
        /// <param name="obj">An object to compare with.</param>
        /// <returns>True if objects are equal.</returns>
        public override bool Equals(object obj)
        {
            if (obj is BooleanFunction)
            {
                return Equals(obj as BooleanFunction);
            }

            return base.Equals(obj);
        }

        /// <summary>
        /// Checks whether boolean function is specified correcly (Domains intersection is empty).
        /// </summary>
        /// <returns>True if domains intersection is empty.</returns>
        public bool CheckDomains()
        {
            if (!Domain0.IsOrthogonalTo(Domain1))
            {
                return false;
            }

            // checking cubes dimensions
            return Domain0.Count == 0 || Domain1.Count == 0 || Domain0.Dimension == Domain1.Dimension;
        }

        /// <summary>
        /// Checks dimensions for all function cubes.
        /// </summary>
        /// <returns>True if all cubes in function have the same dimensions.</returns>
        public bool CheckDimensions()
        {
            var dimension = 0;

            // checking Domain0
            foreach (var cube in Domain0)
            {
                if (dimension == 0)
                {
                    dimension = cube.Dimension;
                }

                if (dimension != cube.Dimension)
                {
                    return false;
                }
            }

            // Checking Domain1
            foreach (var cube in Domain1)
            {
                if (dimension == 0)
                {
                    dimension = cube.Dimension;
                }

                if (dimension != cube.Dimension)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Checks boolean system for consistency (domains, dimensions).
        /// </summary>
        /// <returns>True if the functino is specified correctly.</returns>
        public bool CheckConsistency()
        {
            return CheckDimensions() && CheckDomains();
        }

        /// <summary>
        /// Returns true if boolean function is constant 1.
        /// </summary>
        public bool IsConstant1
        {
            get
            {
                if (!CheckDomains())
                {
                    throw new InvalidOperationException("Boolean function domains are incompatible.");
                }

                return Domain1.IsFullSet;
            }
        }


        /// <summary>
        /// Returns true if boolean function is constant 0.
        /// </summary>
        public bool IsConstant0
        {
            get
            {
                if (!CheckDomains())
                {
                    throw new InvalidOperationException("Boolean function domains are incompatible.");
                }

                return Domain0.IsFullSet;
            }
        }

        /// <summary>
        /// Calculates function value on the cube.
        /// </summary>
        /// <param name="cubeArg">Cube to calculate value on.</param>
        /// <returns>Function value: 0/1/-.</returns>
        public BooleanValue Calculate(Cube cubeArg)
        {
            // checking domains
            if (!CheckDomains())
            {
                throw new InvalidOperationException("Boolean function domains are incompatible.");
            }

            var result = BooleanValue.Undefined;
            if (Domain0.Any(cube => cube.Contains(cubeArg)))
            {
                result = BooleanValue.False;
            }

            if (Domain1.Any(cube => cube.Contains(cubeArg)))
            {
                result = BooleanValue.True;
            }

            return result;
        }

        /// <summary>
        /// Checks value equality of two boolean functions.
        /// </summary>
        /// <param name="f1">First boolean function.</param>
        /// <param name="f2">Second boolean function.</param>
        /// <returns>True if functions are equal in terms of its values.</returns>
        public static bool operator ==(BooleanFunction f1, BooleanFunction f2)
        {
            return !ReferenceEquals(f1, null) && f1.Equals(f2);
        }

        /// <summary>
        /// Checks value inequality of two boolean functions.
        /// </summary>
        /// <param name="f1">First boolean function.</param>
        /// <param name="f2">Second boolean function.</param>
        /// <returns>True if functions are not equal in terms of its values.</returns>
        public static bool operator !=(BooleanFunction f1, BooleanFunction f2)
        {
            return !(f1 == f2);
        }

        /// <summary>
        /// Reads BooleanFunction from external data.
        /// </summary>
        public void Read()
        {
            if (Reader == null)
            {
                throw new InvalidOperationException("Reader object required.");
            }

            // reading object
            Reader.Read();
        }

        /// <summary>
        /// Checks whether boolean function f is realization of another one g (Domain1(f) >= Domain1(g) AND Domain0(f) >= Domain0(g)).
        /// </summary>
        /// <param name="function">Boolean function to check realization relation with.</param>
        /// <returns>True if function is realization of another one.</returns>
        public bool IsRealizationOf(BooleanFunction function)
        {
            return !(function.Domain1.Any(cube => !cube.IsCoveredBy(Domain1)) || function.Domain0.Any(cube => !cube.IsCoveredBy(Domain0)));
        }

        /// <summary>
        /// Builds reduced function as a result of applying Blake algorithm (all generalized glue operations and conjunction covering operations applied).
        /// Reduction only applied to Domain1.
        /// </summary>
        /// <returns>Reduced function with all glue operations applied.</returns>
        public BooleanFunction Reduce()
        {
            var result = new BooleanFunction();

            // initial set with excluded absorbed cubes
            var workingSet = new List<Cube>(Domain1.ExcludeAbsorbedCubes());
            
            // performing generalized glue operation and absorption
            var absorbedCubes = new HashSet<Cube>();
            for (var i = 1; i < workingSet.Count; i++)
            {
                var iteratorCube = workingSet[i];
                if (absorbedCubes.Contains(iteratorCube))
                {
                    continue;
                }

                for (var j = 0; j < i; j++)
                {
                    var compareCube = workingSet[j];
                    if (absorbedCubes.Contains(compareCube))
                    {
                        continue;
                    }

                    var generalizedGlueResult = iteratorCube.GlueGeneralized(compareCube);
                    if (generalizedGlueResult == null)
                    {
                        continue;
                    }

                    // checking whether glue result is absorbed by other cubes
                    var isGlueResultAbsorbed = false;
                    for (var k = 0; k < workingSet.Count; k++)
                    {
                        if (absorbedCubes.Contains(workingSet[k]))
                        {
                            continue;
                        }

                        if (workingSet[k].Contains(generalizedGlueResult))
                        {
                            isGlueResultAbsorbed = true;
                            break;
                        }
                    }

                    if (isGlueResultAbsorbed)
                    {
                        continue;
                    }

                    // excluding all cubes absorbed by glue result from working set
                    for (var k = 0; k < workingSet.Count; k++)
                    {
                        if (absorbedCubes.Contains(workingSet[k]))
                        {
                            continue;
                        }

                        if (!generalizedGlueResult.Contains(workingSet[k]))
                        {
                            continue;
                        }

                        absorbedCubes.Add(workingSet[k]);
                    }

                    // appending glue result to working set
                    workingSet.Add(generalizedGlueResult);
                }
            }

            result.Domain1 = new CubeSet(workingSet.Where(c => !absorbedCubes.Contains(c)));
            return result;
        }
    }
}
